// Proxy to represent a Mission Model in ASL
// Copyright 2005 (C) Ralph Thomas

#include <model/proxy/proxy.h>
#include <model/attributeNames.h>
#include <model/attributeInfo.h>
#include <model/receiver.h>
#include <map>

namespace model {
	//
	/// The proxy class looks after the model, and can
	/// bind all of the model properties to sheet values.
	//
	class proxy : public receiver {
		//
		/// The name_map_t type is used to 
		typedef	std::map<std::string, adobe::name_t> name_map_t;
		model*		_model; ///< The model we currently represent.
		std::string	_url;	///< The URL of the current model.
		name_map_t	_nameMap;	///< Map mission name->cellname
		adobe::sheet_t& _sheet;	///< Sheet to update.
	  public:
		proxy( const adobe::dictionary_t& parameters,
			const adobe::factory_token_t& token );
		virtual ~proxy();
		//
		/// This function is called by Adam when the URL in the sheet
		/// (assuming that the URL came from an Adam cell) changes. It
		/// means that we might need to release our current model and
		/// load the new one.
		///
		/// \param	value	the new URL wrapped in an 
		///			adobe::value_t.
		//
		void update_url( const adobe::value_t& value );
		//
		/// This function is an interface to setAttribute. It does
		/// almost the same thing as setAttribute, except that it first
		/// checks if _model is NULL and always calls into the current
		/// _model (which would require fancy rebinding and connection
		/// tracking if we were to remove this function).
		///
		/// \param	attr	the attribute to set.
		/// \param	value	the new value wrapped in an
		///			adobe::value_t.
		//
		void setAttribute( const std::string& attr, const adobe::value_t&
				value );
		//
		// from model/receiver.h
		//
		void notify( const model* m, const std::string& attrName );
	};
	//
	// Model proxy implementation.
	//
	proxy::proxy( const adobe::dictionary_t& parameters, const
			adobe::factory_token_t& token )
	 : _model( 0 ), _sheet( token.sheet_m ) {
		//
		// Everything in the parameters is taken as a cell name. We do
		// some special wiring for "url" (which needs to use the
		// proxy::update_url function to change.
		//
		adobe::name_t url;
		//
		// Get the URL out of the parameter dictionary. It will be
		// ignored by the while-loop iterating through the dictionary
		// because there is no translation for "url" in attrDictionary.
		//
		parameters.get<adobe::name_t>( adobe::static_name_t( "url" ),
			url );
		//
		// It's an error if we didn't get a URL.
		//
		if( !url ) throw std::runtime_error( "No URL cell specified for"
			" a model." );
		//
		// Bind the URL to the named cell in the sheet.
		//
		token.assemblage_m.hold_connection( token.sheet_m.monitor_value(
			url, boost::bind( &proxy::update_url, this, _1 ) ) );
		//
		// Now wire up all of the other parameters.
		//
		adobe::dictionary_t::const_iterator first = parameters.begin();
		adobe::dictionary_t::const_iterator last = parameters.end();
		//
		// Try to take the key as a name to be looked up in a
		// dictionary, the value to be the cell name to bind against.
		//
		while( first != last ) {
			adobe::name_t sheetName;
			std::string missionName( first->first.get() );
			first->second.get<adobe::name_t>( sheetName );
			//
			// Make sure that we actually got a name from that, if
			// not then we need to go onto the next one.
			//
			if( !sheetName || missionName.empty() ) {
				first++;
				continue;
			}
			//
			// Bind the sheet cell to setAttribute.
			//
			token.assemblage_m.hold_connection(
				token.sheet_m.monitor_value( sheetName,
					boost::bind( &proxy::setAttribute, this,
						missionName, _1 ) ) );
			//
			// We don't actually wire up the model's receiver to the
			// sheet directly. Instead we make a map of names to
			// cell names. In that way the receiver function can
			// lookup the name of the cell to update.
			//
			if( !missionName.empty() && missionName != "url" )
				_nameMap[missionName] = sheetName;
			first++;
		}
	}
	proxy::~proxy() {
		if( _model ) {
			_model->unsubscribe( this );
			_model->release();
		}
		_model = 0;
	}
	void proxy::update_url( const adobe::value_t& value ) {
		//
		// The URL is inside the given value. Get it out and see if it's
		// the same as the one we already have.
		//
		std::string url = _url;
		//
		// If we can't get the new URL from the value then give up.
		//
		if( !value.get( url ) ) return;
		//
		// If the new URL is the same as the old URL then we don't have
		// anything to do anyway.
		//
		if( _url == url ) return;
		//
		// We need to unsubscribe from and release the current model
		// before we create the new one.
		//
		if( _model ) {
			_model->unsubscribe( this );
			_model->release();
			_model = 0;
		}
		//
		// Now we can try to create the new model.
		// XXX: What should happen if the model can't be created? For
		// XXX: now I'll throw an exception and see what happens.
		//
		_model = model::fromString( url );
		if( _model ) {
			//
			// Alright! We were able to get a reference to the named
			// model. Let's subscribe, update our idea of what the
			// current URL is and return.
			//
			_model->subscribe( this );
			_url = url;
			//
			// Potentially everything inside the model has changed,
			// update all of the cells which refer to this model.
			//
			name_map_t::iterator first = _nameMap.begin();
			name_map_t::iterator last = _nameMap.end();
			while( first != last ) {
				_sheet.set( first->second,
					_model->getAttribute( first->first ) );
				first++;
			}
			_sheet.update();
			return;
		}
		//
		// We couldn't create the named model. Error out.
		//
		_url = "";
//		throw std::runtime_error( "Couldn't create model: " + url );
		
	}
	void proxy::setAttribute( const std::string& attr,
		const adobe::value_t& value ) {
		//
		// If the model doesn't exist or the attribute is zero, then we
		// don't have anything to do.
		//
		if( attr.empty() || !_model ) return;
		//
		// We need to debounce. We shouldn't set a value to it's current
		// value.
		//
		if( _model->getAttribute( attr ) == value ) return;
		//
		// The model exists, and the attribute is probably good, so we
		// can safely set the attribute.
		//
		_model->setAttribute( attr, value );
	}
	void proxy::notify( const model* m, const std::string& attr ) {
		//
		// Something changed in the model. If it's something we care
		// about then it will be in the _nameMap (telling us which cell
		// to update).
		//
		name_map_t::iterator i = _nameMap.find( attr );
		if( i == _nameMap.end() ) return;
		//
		// Set the new value in the sheet.
		//
		_sheet.set( i->second, m->getAttribute( attr ) );
		_sheet.update();
	}
	//
	// This function creates and returns the proxy factory.
	//
	adobe::widget_node_t model_proxy_factory(
		const adobe::dictionary_t&	parameters,
		const adobe::widget_node_t&	parent,
		const adobe::factory_token_t&	token ) {
		//
		// We don't actually make our own token -- we just return our
		// parent's one. All we care about is creating a proxy object
		// and getting it to be deleted when the sheet goes away.
		//
		proxy* us = 0;
		try {
			//
			// The constructor can throw exceptions. Make sure that
			// we don't leak memory if that happens.
			//
			us = new proxy( parameters, token );
		} catch( ... ) {
			if( us ) delete us;
			throw;
		}
		//
		// Register the proxy to get deleted at the appropriate time.
		//
		token.assemblage_m.delete_on_destruction( us );
		return parent;
	}
};

